﻿using System;
using System.Collections;
using System.Collections.Generic;


namespace HRacing
{
    public class DelayCall
    {
        public static readonly int INFINITE = -1;
        public static readonly uint INVALID_ID = 0;

        static Dictionary<uint, CallItem> calls = new Dictionary<uint, CallItem>();
        static List<uint> active_id = new List<uint>(128);
        static List<uint> need_del_id = new List<uint>(64);
        static Queue<CallItem> call_cache = new Queue<CallItem>();
        static uint ID = INVALID_ID + 1;
        static float cur_ticks = 0f;

        static CallItem allocate_item()
        {
            CallItem item = null;
            if (call_cache.Count > 0)
                item = call_cache.Dequeue();
            else
                item = new CallItem();

            item.id = ID++;
            if (ID == uint.MaxValue) ID = INVALID_ID + 1;
            return item;
        }

        static void deallocate_item(CallItem item)
        {
            call_cache.Enqueue(item);
        }

        public static uint Add(Action func, float delay_time)
        {
            return Add(func, delay_time, 1);
        }

        public static uint Add(Action func, float delay_time, int repeat)
        {
            if (delay_time <= 0f || repeat == 0)
                return INVALID_ID;
            if (repeat < 0) repeat = INFINITE;

            var item = allocate_item();
            item.func = func;
            item.time = cur_ticks;
            item.repeat = repeat;
            item.delay_time = Math.Max(0f, delay_time);
            item.is_pause = false;

            calls.Add(item.id, item);
            active_id.Add(item.id);
            return item.id;
        }

        public static bool Del(uint id)
        {
            if (calls.ContainsKey(id))
            {
                deallocate_item(calls[id]);
                active_id.Remove(id);
                calls.Remove(id);
                return true;
            }
            return false;
        }

        public static void DelAll()
        {
            foreach (var item in calls)
            {
                deallocate_item(item.Value);
            }
            active_id.Clear();
            calls.Clear();
            call_cache.Clear();
            need_del_id.Clear();
        }

        public static bool Pause(uint id)
        {
            if (!calls.ContainsKey(id))
            {
                return false;
            }

            var obj = calls[id];
            // 记录暂停时刻差值，恢复时会等待该差值再执行
            var time_span = Math.Max(cur_ticks - obj.time, 0f);
            obj.time = time_span;
            calls[id].is_pause = true;
            return true;
        }

        public static bool IsPause(uint id)
        {
            if (!calls.ContainsKey(id))
            {
                return false;
            }

            return calls[id].is_pause;
        }

        public static void PauseAll()
        {
            foreach (var item in calls)
            {
                Pause(item.Value.id);
            }
        }

        public static bool Resume(uint id)
        {
            if (!calls.ContainsKey(id))
            {
                return false;
            }

            var obj = calls[id];
            var time_span = obj.time;
            obj.time = cur_ticks - time_span;
            calls[id].is_pause = false;
            return true;
        }

        public static void ResumeAll()
        {
            foreach (var item in calls)
            {
                Resume(item.Value.id);
            }
        }

        public static void OnUpdate(float delta_time)
        {
            // ignore invalid data
            if (delta_time >= 10f) return;

            try
            {
                cur_ticks += delta_time;

                need_del_id.Clear();
                //使用active_id是因为触发call过程可能会添加新的call, 导致Dictionary的Enumrator失效
                for (int i = 0; i < active_id.Count; i++)
                {
                    var cid = active_id[i];
                    if (!calls.ContainsKey(cid))
                        continue;

                    var obj = calls[active_id[i]];
                    if (obj.is_pause)
                        continue;

                    if (cur_ticks - obj.time >= obj.delay_time)
                    {
                        obj.func();
                        if (obj.repeat != INFINITE && --obj.repeat <= 0)
                            need_del_id.Add(obj.id);
                        else
                            obj.time += obj.delay_time;
                    }
                }

                for (int i = 0; i < need_del_id.Count; i++)
                {
                    Del(need_del_id[i]);
                }
            }
            catch (Exception err)
            {
                LogManager.GetInstance().LogFormat("DelayCall Error. {0} \r\n{1}", err.Message, err.StackTrace);
            }
        }

        private class CallItem
        {
            public uint id;
            public Action func = null;
            public float time;
            public int repeat = 1;
            public float delay_time = 0;
            public bool is_pause = false;
        }
    }
}